package com.atguigu.gmall.gateway.filter;

import com.atguigu.gmall.common.exception.GmallUserException;
import com.atguigu.gmall.common.utils.IpUtils;
import com.atguigu.gmall.common.utils.JwtUtils;
import com.atguigu.gmall.gateway.config.JwtProperties;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

//局部作用filter 创建的是filter工厂类，使用时会根据配置该工厂的路由 来创建filter对象使用
@Component
@EnableConfigurationProperties(JwtProperties.class)
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.AuthPathConfig> {
    @Resource
    JwtProperties jwtProperties;
    //配置由父类 来解析配置的路由中该工厂类绑定的参数
    public AuthGatewayFilterFactory() {
        super(AuthPathConfig.class);
    }

    //创建局部filter对象的方法
    @Override
    public GatewayFilter apply(AuthPathConfig config) {
        return (exchange, chain) -> {
//            System.out.println(config.getClass().getName());
//            System.out.println(config);
//            System.out.println("局部作用filter 拦截到了请求");
            //当此方法调用时，代表一个请求访问的服务需要该过滤器处理
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();
            //1、验证访问的资源是否需要验证登录状态
            //如果不需要验证  直接放行
            List<String> paths = config.getPaths();//需要鉴权的路径集合
            String path = request.getURI().getPath();//获取请求路径
            if(paths.stream().allMatch(p-> p.indexOf(path) == -1)){
                return chain.filter(exchange);
            }
            //2、如果需要验证：获取请求报文中的token
            //前端异步请求： token会存在请求头中: token ：TOKEN字符串
            // 前端同步请求：token会存在cookie中(GMALL-TOKEN)
            String token = request.getHeaders().getFirst("token");
            if(StringUtils.isEmpty(token)){
                //再从cookie中获取token
                MultiValueMap<String, HttpCookie> cookies = request.getCookies();
                if(!CollectionUtils.isEmpty(cookies) && cookies.containsKey("GMALL-TOKEN")){
                    token = cookies.getFirst("GMALL-TOKEN").getValue();
                }
            }
            System.out.println("path : "+ request.getURI().toString());
            try {
                //如果获取失败  重定向到登录页面
                if(StringUtils.isEmpty(token)){
                    throw new GmallUserException("没有登录",1002);
                }
                //3、如果获取token成功： 解析token
                Map<String, Object> map = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey());
                // 192.168.1.171是虚拟机ip
                //用户登录应该保存客户端的ip：
                String ip = map.get("ip").toString();
                String ipAddressAtGateway = IpUtils.getIpAddressAtGateway(request);
                if(!StringUtils.equals(ip,ipAddressAtGateway)){
                    throw new GmallUserException("ip地址错误",1001);
                }
                //4、token解析成功：获取token中的userId 透传给后续的微服务
                String userId = map.get("userId").toString();
                //为了减少后续服务解析token的业务，可以将userId设置到请求报文中交给后续的服务使用
                request.mutate().header("userId",userId).build();//修改request对象
                exchange.mutate().request(request).build();//将修改后的request对象设置到exchange中
                //5、放行
                return chain.filter(exchange);//使用修改后的exchange对象放行
            } catch (Exception e) {
                //e.printStackTrace();
                //如果解析失败： token过期/token被篡改/验证ip地址不匹配  重定向到登录页面
                response.setStatusCode(HttpStatus.SEE_OTHER);
                response.getHeaders().setLocation(URI.create("http://sso.gmall.com/toLogin.html?returnUrl="
                        +request.getURI().toString()));
                return response.setComplete();
            }
        };
    }
    //配置如何解析 Auth配置的参数：
    @Override
    public ShortcutType shortcutType() {
        //将Auth配置的参数 以,分隔为多个元素存到list中
        return ShortcutType.GATHER_LIST;
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("paths");//参数一定要和AuthPathConfig接收参数的属性名一样
    }

    //如果当前类的类名按照规范:xxxGatewayFilterFactory编写 此方法可以省略
    // 当前类 filter工厂默认名称为 Auth，否则需要自己的name()中返回期望的filter工厂名称
//    @Override
//    public String name() {
//        return "auth234";
//    }
    //接收Auth过滤器 配置的参数的配置类
    @Data
    public static class AuthPathConfig{
        private List<String> paths;
    }
}
